﻿namespace ImageProcessing
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;

    /// <summary>
    /// OpenImage class to open multiple image extensions.
    /// </summary>
    public class OpenImage
    {
        /// <summary>
        /// Read int number from a file by a Binary Reader.
        /// </summary>
        /// <param name="r"> Binary Reader.</param>
        /// <returns>
        /// Int Number
        /// </returns>
        public static int Readint(ref BinaryReader r)
        {
            char c = r.ReadChar();
            string s = string.Empty;
            s += c;
            while (!char.IsWhiteSpace(c))
            {
                c = r.ReadChar();
                s += c;
            }

            return int.Parse(s);
        }

        /// <summary>
        /// Loads the image.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <param name="grayScale">if set to <c>true</c> [gray scale].</param>
        /// <returns>
        /// Image's matrix
        /// </returns>
        public static ImageStructure[,] LoadBMP(string filePath, out bool grayScale)
        {
            Bitmap bmp = new Bitmap(filePath);
            ImageStructure[,] image = new ImageStructure[bmp.Height, bmp.Width];

            int width = 0;

            bool format8 = false;
            bool format32 = false;
            bool format24 = false;

            if (bmp.PixelFormat == PixelFormat.Format24bppRgb)
            {
                format24 = true;
                width = bmp.Width * 3;
            }
            else if (bmp.PixelFormat == PixelFormat.Format32bppArgb || bmp.PixelFormat == PixelFormat.Format32bppPArgb || bmp.PixelFormat == PixelFormat.Format32bppRgb)
            {
                format32 = true;
                width = bmp.Width * 4;
            }
            else if (bmp.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                format8 = true;
                width = bmp.Width;
                grayScale = true;
            }

            unsafe
            {
                BitmapData bmpD = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
                int pointerAddress = (int)bmpD.Scan0;
                int extraBytes = bmpD.Stride - width;
                int columnsPerThread = bmp.Height / Environment.ProcessorCount;
                int minHeight = 0, maxHeight = 0;
                Thread t = null;
                Semaphore s = new Semaphore(1, 1000);
                List<Thread> threadList = new List<Thread>();
                for (int i = 1; i < Environment.ProcessorCount; i += 0)
                {
                    s.WaitOne();
                    minHeight = i * columnsPerThread;
                    maxHeight = (i + 1) * columnsPerThread;
                    if (i == (Environment.ProcessorCount - 1))
                    {
                        t = new Thread(delegate() { new OpenImage().LoadBMPThreadingLoop(s, minHeight, bmp.Height, bmp.Width, extraBytes, pointerAddress, image, format8, format24, format32); });
                        t.Start();
                        threadList.Add(t);
                        break;
                    }

                    t = new Thread(delegate() { new OpenImage().LoadBMPThreadingLoop(s, minHeight, maxHeight, bmp.Width, extraBytes, pointerAddress, image, format8, format24, format32); });
                    t.Start();
                    threadList.Add(t);
                    i++;
                }

                new OpenImage().LoadBMPThreadingLoop(s, 0, columnsPerThread, bmp.Width, extraBytes, pointerAddress, image, format8, format24, format32);
                foreach (Thread thread in threadList)
                {
                    thread.Join();
                }

                bmp.UnlockBits(bmpD);
            }

            if (format8)
            {
                grayScale = true;
            }
            else
            {
                grayScale = false;
            }

            return image;
        }

        /// <summary>
        /// Opens the p3 image.
        /// </summary>
        /// <param name="filepath">The filepath.</param>
        /// <returns>
        /// ImageStructure matrix
        /// </returns>
        public static ImageStructure[,] OpenP3Image(string filepath)
        {
            Bitmap ppmP3Bitmap = null;
            BitmapData bmpD = null;
            FileStream fileStr = new FileStream(filepath, FileMode.Open);
            StreamReader readFile = new StreamReader(fileStr);

            if (readFile.ReadLine().ToLower() != "p3")
            {
                return null;
            }

            if (readFile.ReadLine()[0] != '#')
            {
                return null;
            }

            string[] widthHeight = readFile.ReadLine().Split(' ');
            int width = Convert.ToInt32(widthHeight[0]), height = Convert.ToInt32(widthHeight[1]);
            if (readFile.ReadLine() != "255")
            {
                return null;
            }

            ppmP3Bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            ImageStructure[,] image = new ImageStructure[height, width];
            unsafe
            {
                bmpD = ppmP3Bitmap.LockBits(new Rectangle(0, 0, ppmP3Bitmap.Width, ppmP3Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                byte* p = (byte*)bmpD.Scan0;
                int extraBytes = bmpD.Stride - (width * 3);
                string imageString = readFile.ReadToEnd();
                int stringIndex = 0;
                string byteValue = string.Empty;
                for (int i = 0; i < height; i++)
                {
                    for (int j = 0; j < width; j++)
                    {
                        while (!char.IsWhiteSpace(imageString[stringIndex]))
                        {
                            byteValue += imageString[stringIndex];
                            stringIndex++;
                        }

                        p[2] = image[i, j].B = byte.Parse(byteValue);
                        stringIndex++;
                        if (imageString[stringIndex] == '\n')
                        {
                            stringIndex++;
                        }

                        byteValue = string.Empty;
                        while (!char.IsWhiteSpace(imageString[stringIndex]))
                        {
                            byteValue += imageString[stringIndex];
                            stringIndex++;
                        }

                        stringIndex++;
                        p[1] = image[i, j].G = byte.Parse(byteValue);
                        if (imageString[stringIndex] == '\n')
                        {
                            stringIndex++;
                        }

                        byteValue = string.Empty;
                        while (!char.IsWhiteSpace(imageString[stringIndex]))
                        {
                            byteValue += imageString[stringIndex];
                            stringIndex++;
                        }

                        stringIndex++;
                        p[0] = image[i, j].R = byte.Parse(byteValue);
                        byteValue = string.Empty;
                        if (imageString[stringIndex] == '\n')
                        {
                            stringIndex++;
                        }

                        p += 3;
                    }

                    p += extraBytes;
                }

                ppmP3Bitmap.UnlockBits(bmpD);
            }

            return image;
        }

        /// <summary>
        /// Loads the p6.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <returns>
        /// ImageStructure matrix
        /// </returns>
        public static ImageStructure[,] LoadP6(string filePath)
        {
            FileStream stream = new FileStream(filePath, FileMode.Open);
            BinaryReader binReader = new BinaryReader(stream);
            int width = 0;
            int height = 0;
            int depth = 0;
            while (true)
            {
                char c = (char)binReader.PeekChar();
                if (char.IsWhiteSpace(c))
                {
                    binReader.Read();
                }
                else if (c == '#')
                {
                    while (binReader.ReadChar() != '\n')
                    {
                    }

                    continue;
                }
                else if (c == 'p' || c == 'P')
                {
                    binReader.ReadChars(2);
                }
                else if (c >= 48 && c <= 57)
                {
                    string tempS = new string(binReader.ReadChars(3));
                    binReader.Read();
                    width = int.Parse(tempS);

                    tempS = new string(binReader.ReadChars(3));
                    binReader.Read();
                    height = int.Parse(tempS);

                    tempS = new string(binReader.ReadChars(3));
                    c = (char)binReader.PeekChar();
                    if (char.IsWhiteSpace(c))
                    {
                        binReader.Read();
                    }

                    depth = int.Parse(tempS);

                    break;
                }
            }

            byte[] pixels = binReader.ReadBytes((int)(binReader.BaseStream.Length - binReader.BaseStream.Position));
            binReader.Close();
            stream.Close();
            ImageStructure[,] image = new ImageStructure[height, width];
            Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            unsafe
            {
                BitmapData bmpD = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                int pointerAddress = (int)bmpD.Scan0;
                int extraBytes = bmpD.Stride - (width * 3);
                int columnsPerThread = height / Environment.ProcessorCount;
                int minHeight = 0, maxHeight = 0;
                Thread t = null;
                Semaphore s = new Semaphore(1, 1000);
                List<Thread> threadList = new List<Thread>();
                for (int i = 1; i < Environment.ProcessorCount; i += 0)
                {
                    s.WaitOne();
                    minHeight = i * columnsPerThread;
                    maxHeight = (i + 1) * columnsPerThread;
                    if (i == (Environment.ProcessorCount - 1))
                    {
                        t = new Thread(delegate() { new OpenImage().LoadP6ThreadingLoop(s, minHeight, height, pixels, width, image, pointerAddress, extraBytes); });
                        t.Start();
                        threadList.Add(t);
                        break;
                    }

                    t = new Thread(delegate() { new OpenImage().LoadP6ThreadingLoop(s, minHeight, maxHeight, pixels, width, image, pointerAddress, extraBytes); });
                    t.Start();
                    threadList.Add(t);
                    i++;
                }

                new OpenImage().LoadP6ThreadingLoop(s, 0, columnsPerThread, pixels, bmp.Width, image, pointerAddress, extraBytes);
                foreach (Thread thread in threadList)
                {
                    thread.Join();
                }

                bmp.UnlockBits(bmpD);
            }

            return image;
        }

        /// <summary>
        /// Opens the p5 image.
        /// </summary>
        /// <param name="filepath">The filepath.</param>
        /// <param name="grayscale">if set to <c>true</c> [grayscale].</param>
        /// <returns>
        /// 2D array of ImageStructure represent the image
        /// </returns>
        public static ImageStructure[,] OpenP5Image(string filepath, out bool grayscale)
        {
            grayscale = true;
            FileStream stream = new FileStream(filepath, FileMode.Open);
            BinaryReader binReader = new BinaryReader(stream);
            int width, height, maxVal;
            ImageStructure[,] image;
            char cchar;

            // Start
            cchar = binReader.ReadChar();

            if (cchar != 'p' && cchar != 'P')
            {
                return null;
            }
            else
            {
                // The rest of the pre line
                while (cchar != '\n')
                {
                    cchar = binReader.ReadChar();
                }

                // Next line 
                while (true)
                {
                    cchar = (char)binReader.PeekChar();
                    if (cchar == '#')
                    {
                        while (cchar != '\n')
                        {
                            cchar = binReader.ReadChar();
                        }
                    }
                    else
                    {
                        break;
                    }
                }

                // Read width and height
                width = Readint(ref binReader);
                height = Readint(ref binReader);
                image = new ImageStructure[height, width];

                // Read max value
                maxVal = Readint(ref binReader);

                byte[] pixels = binReader.ReadBytes((int)(binReader.BaseStream.Length - binReader.BaseStream.Position));
                binReader.Close();
                stream.Close();

                int rowsPerThread = height / Environment.ProcessorCount;
                int rowsRe = height % Environment.ProcessorCount;
                int minHeight = 0, maxHeight = 0;
                Thread t = null;
                Semaphore s = new Semaphore(1, 1000);
                List<Thread> threadList = new List<Thread>();
                for (int i = 1; i < Environment.ProcessorCount; i++)
                {
                    s.WaitOne();
                    minHeight = i * rowsPerThread;
                    maxHeight = (i + 1) * rowsPerThread;
                    if (i == (Environment.ProcessorCount - 1))
                    {
                        t = new Thread(delegate() { new OpenImage().OpenP5Threading(s, image, minHeight, height, width, pixels); });
                        t.Start();
                        threadList.Add(t);
                        break;
                    }

                    t = new Thread(delegate() { new OpenImage().OpenP5Threading(s, image, minHeight, maxHeight, width, pixels); });
                    t.Start();
                    threadList.Add(t);
                }

                new OpenImage().OpenP5Threading(s, image, 0, rowsPerThread, width, pixels);
                foreach (Thread thread in threadList)
                {
                    thread.Join();
                }

                return image;
            }
        }

        /// <summary>
        /// Converts the structure to bitmap.
        /// </summary>
        /// <param name="img">The img.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <returns>Structure to bitmap</returns>
        public static Bitmap ConvertStructureToBitmap(ImageStructure[,] img, int width, int height)
        {
            Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            unsafe
            {
                BitmapData bmpD = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                int pointerAddress = (int)bmpD.Scan0;
                int extraBytes = bmpD.Stride - (width * 3);
                int rowsPerThread = bmp.Height / Environment.ProcessorCount;
                int minHeight = 0, maxHeight = 0;
                Thread t = null;
                Semaphore s = new Semaphore(1, 1000);
                List<Thread> threadList = new List<Thread>();
                for (int i = 1; i < Environment.ProcessorCount; i++)
                {
                    s.WaitOne();
                    minHeight = i * rowsPerThread;
                    maxHeight = (i + 1) * rowsPerThread;
                    if (i == (Environment.ProcessorCount - 1))
                    {
                        t = new Thread(delegate() { new OpenImage().ConvertStructureToBitmapThreading(s, minHeight, height, width, extraBytes, pointerAddress, img); });
                        t.Start();
                        threadList.Add(t);
                        break;
                    }

                    t = new Thread(delegate() { new OpenImage().ConvertStructureToBitmapThreading(s, minHeight, maxHeight, width, extraBytes, pointerAddress, img); });
                    t.Start();
                    threadList.Add(t);
                }

                t = new Thread(delegate() { new OpenImage().ConvertStructureToBitmapThreading(s, 0, rowsPerThread, width, extraBytes, pointerAddress, img); });
                t.Start();
                threadList.Add(t);

                foreach (Thread item in threadList)
                {
                    item.Join();
                }

                bmp.UnlockBits(bmpD);
            }

            return bmp;
        }

        /// <summary>
        /// Opens the p5 threading.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="image">The image.</param>
        /// <param name="minHeight">Height of the min.</param>
        /// <param name="maxHeight">Height of the max.</param>
        /// <param name="width">The width.</param>
        /// <param name="pixels">The pixels.</param>
        private void OpenP5Threading(Semaphore s, ImageStructure[,] image, int minHeight, int maxHeight, int width, byte[] pixels)
        {
            s.Release();
            int bytePosition = minHeight * width;
            for (int i = minHeight; i < maxHeight; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    image[i, j].R = image[i, j].G = image[i, j].B = pixels[bytePosition];
                    bytePosition++;
                }
            }
        }

        /// <summary>
        /// Loads the p6 threading loop.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="minHeight">Height of the min.</param>
        /// <param name="maxHeight">Height of the max.</param>
        /// <param name="pixels">The pixels.</param>
        /// <param name="width">The width.</param>
        /// <param name="image">The image.</param>
        /// <param name="pointerAddress">The p address.</param>
        /// <param name="extraBytes">The extra bytes.</param>
        private void LoadP6ThreadingLoop(Semaphore s, int minHeight, int maxHeight, byte[] pixels, int width, ImageStructure[,] image, int pointerAddress, int extraBytes)
        {
            unsafe
            {
                s.Release();
                int byteIndex = minHeight * width * 3;
                int pointerIndex = byteIndex + (minHeight * extraBytes);
                byte* p = (byte*)pointerAddress;
                for (int i = minHeight; i < maxHeight; i++)
                {
                    for (int j = 0; j < width; j++)
                    {
                        image[i, j].R = p[pointerIndex] = pixels[byteIndex + 2];
                        image[i, j].G = p[pointerIndex + 1] = pixels[byteIndex + 1];
                        image[i, j].B = p[pointerIndex + 2] = pixels[byteIndex];

                        byteIndex += 3;
                        pointerIndex += 3;
                    }

                    pointerIndex += extraBytes;
                }

                return;
            }
        }

        /// <summary>
        /// Loads the BMP threading loop.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="minHeight">Height of the min.</param>
        /// <param name="maxHeight">Height of the max.</param>
        /// <param name="width">The width.</param>
        /// <param name="extraBytes">The extra bytes.</param>
        /// <param name="pointerAddress">The p address.</param>
        /// <param name="image">The image.</param>
        /// <param name="format8">if set to true, then it is [format8].</param>
        /// <param name="format24">if set to true, then it is [format24].</param>
        /// <param name="format32">if set to true, then it is [format32].</param>
        private void LoadBMPThreadingLoop(Semaphore s, int minHeight, int maxHeight, int width, int extraBytes, int pointerAddress, ImageStructure[,] image, bool format8, bool format24, bool format32)
        {
            unsafe
            {
                s.Release();
                int pointerIndex = 0;
                if (format8)
                {
                    pointerIndex = (minHeight * width) + (minHeight * extraBytes);
                }
                else if (format24)
                {
                    pointerIndex = (minHeight * width * 3) + (minHeight * extraBytes);
                }
                else if (format32)
                {
                    pointerIndex = (minHeight * width * 4) + (minHeight * extraBytes);
                }

                byte* p = (byte*)pointerAddress;
                for (int i = minHeight; i < maxHeight; i++)
                {
                    for (int j = 0; j < width; j++)
                    {
                        if (format8)
                        {
                            image[i, j].R = image[i, j].G = image[i, j].B = p[pointerIndex];
                            p++;
                        }
                        else
                        {
                            image[i, j].R = p[pointerIndex];
                            image[i, j].G = p[pointerIndex + 1];
                            image[i, j].B = p[pointerIndex + 2];
                            if (format24)
                            {
                                pointerIndex += 3;
                            }
                            else if (format32)
                            {
                                pointerIndex += 4;
                            }
                        }
                    }

                    pointerIndex += extraBytes;
                }

                return;
            }
        }

        /// <summary>
        /// Converts the structure to bitmap threading.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="minHeight">Height of the min.</param>
        /// <param name="maxHeight">Height of the max.</param>
        /// <param name="width">The width.</param>
        /// <param name="extraBytes">The extra bytes.</param>
        /// <param name="pointerAddress">The pointer address.</param>
        /// <param name="img">The img.</param>
        private void ConvertStructureToBitmapThreading(Semaphore s, int minHeight, int maxHeight, int width, int extraBytes, int pointerAddress, ImageStructure[,] img)
        {
            s.Release();
            pointerAddress = pointerAddress + (minHeight * width * 3) + (minHeight * extraBytes);
            unsafe
            {
                byte* pointer = (byte*)pointerAddress;
                for (int i = minHeight; i < maxHeight; i++)
                {
                    for (int j = 0; j < width; j++)
                    {
                        pointer[0] = img[i, j].R;
                        pointer[1] = img[i, j].G;
                        pointer[2] = img[i, j].B;

                        pointer += 3;
                    }

                    pointer += extraBytes;
                }
            }
        }

        public static void OpenBitmapFast(string filePath)
        {
            Bitmap img = new Bitmap(filePath);
            PictureBox picBox = new PictureBox();
            picBox.Image = img;
            picBox.SizeMode = PictureBoxSizeMode.AutoSize;
            Main.Instance.MainTabContainer.Add(filePath);
            Main.Instance.MainTabContainer.UpdateViewPanel();
            Main.Instance.MainTabContainer.tempPanel.Controls.Add(picBox);
        }
    }
}